﻿Imports Hinshelwood.TFSStickyBuddy.Modules.Interfaces
Imports System.Windows.Threading
Imports System.Windows.Media.Animation
Imports Hinshelwood.TFSStickyBuddy.HierarchicalWorkItems

Namespace Views

    Partial Public Class TreeByView
        Implements ITreeByView

        ' Amount of the diagram that cannot be scrolled, this 
        ' ensures that some of the diagram is always visible.
        Private Const PanMargin As Double = 50

        ' Duration to pause before auto centering the diagram.
        Private Const AutoCenterAnimationPauseDuration As Double = 1000

        ' Duration of the auto center animation.
        Private Const AutoCenterAnimationDuration As Double = 600


        ' Used when manually scrolling.
        Private scrollStartPoint As Point
        Private scrollStartOffset As Point

        ' Stores the top-left offset of the diagram. Used to auto-scroll
        ' the new primary node to the location of the previous selected node.
        Private previousTopLeftOffset As Point

        ' Timer that is used when animating a new diagram.
        Private autoCenterTimer As DispatcherTimer


        ''' <summary>
        ''' Get or set the zoom level of the diagram.
        ''' </summary>
        Public Property Zoom() As Double
            Get
                'Return ZoomSlider.Value
            End Get
            Set(ByVal value As Double)
                If value >= ZoomSlider.Minimum AndAlso value <= ZoomSlider.Maximum Then
                    Diagram.Scale = value
                    ZoomSlider.Value = value
                    UpdateScrollSize()
                End If
            End Set
        End Property

        Public Property Model() As ITreeByPresentationModel Implements ITreeByView.Model
            Get
                Return Me.DataContext
            End Get
            Set(ByVal value As Interfaces.ITreeByPresentationModel)
                Me.DataContext = value
            End Set
        End Property

        Public Sub New()

            ' This call is required by the Windows Form Designer.
            InitializeComponent()

            ' Add any initialization after the InitializeComponent() call.
            ' Default zoom level.
            Me.Zoom = 1
            ' Timer used for animations.
            autoCenterTimer = New DispatcherTimer()
        End Sub

        Private m_ShowViewCount As Integer = 0

        Public Sub ShowView() Implements Interfaces.ITreeByView.ShowView
            Me.Diagram.ItemWrapperItems = Model.ItemWrappers
            Me.Visibility = Windows.Visibility.Visible
            Dim x As New TextBlock
            m_ShowViewCount = m_ShowViewCount + 1
            x.Text = String.Format("ShowView {0}", m_ShowViewCount)
            Grid.Children.Add(x)
        End Sub

#Region " Event Handlers "

        Private Sub Diagram_DiagramPopulated(ByVal sender As Object, ByVal e As System.EventArgs) Handles Diagram.DiagramPopulated
            ' The diagram was populated. Need to force the diagram to layout 
            ' since the diagram values are required to perform animations (need 
            ' to know exactly where the primary node is located).

            ' Save the current top-left offset before force the layout,
            ' this is used later when animating the diagram.
            Dim offset As Point = GetTopLeftScrollOffset()
            previousTopLeftOffset = New Point( _
                Grid.ActualWidth - ScrollViewer.HorizontalOffset - offset.X, _
                Grid.ActualHeight - ScrollViewer.VerticalOffset - offset.Y)

            ' Force the layout.
            Me.UpdateLayout()

            ' Now auto-scroll so the primary node appears at the previous
            ' selected node location.
            AutoScrollToSelected()

        End Sub

        Private Sub Diagram_DiagramUpdated(ByVal sender As Object, ByVal e As System.EventArgs) Handles Diagram.DiagramUpdated

        End Sub

        Private Sub Diagram_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Diagram.Loaded
            ' Initialize the display after the diagram has been loaded,
            ' set the scroll size and center the diagram in the display area.
            UpdateScrollSize()
            AutoScrollToSelected()
        End Sub

        Private Sub Diagram_SizeChanged(ByVal sender As Object, ByVal e As System.Windows.SizeChangedEventArgs) Handles Diagram.SizeChanged, Me.SizeChanged
            ' Update the scroll size, this is necessary to make sure the
            ' user cannot scroll the entire diagram off the display area.
            UpdateScrollSize()
        End Sub

        Private Sub ZoomSlider_MouseDoubleClick(ByVal sender As Object, ByVal e As System.Windows.Input.MouseButtonEventArgs) Handles ZoomSlider.MouseDoubleClick
            ' Reset the zoom level.
            ZoomSlider.Value = 1.0
        End Sub

        Private Sub ZoomSlider_ValueChanged(ByVal sender As Object, ByVal e As System.Windows.RoutedPropertyChangedEventArgs(Of Double)) Handles ZoomSlider.ValueChanged
            ' Update the diagram zoom level.
            Me.Zoom = e.NewValue
        End Sub

        Private Sub ScrollViewer_ScrollChanged(ByVal sender As Object, ByVal e As System.Windows.Controls.ScrollChangedEventArgs) Handles ScrollViewer.ScrollChanged
            ' Need to adjust the scroll position when zoom changes to keep 
            ' the diagram centered. The ScrollChanged event occurs when
            ' zooming since the diagram's extent changes.

            If Not e.ExtentWidthChange = 0 AndAlso Not e.ExtentWidthChange = e.ExtentWidth Then
                ' Keep centered horizontaly.
                Dim percent As Double = e.ExtentWidthChange / (e.ExtentWidth - e.ExtentWidthChange)
                Dim middle As Double = e.HorizontalOffset + (e.ViewportWidth / 2)
                ScrollViewer.ScrollToHorizontalOffset(e.HorizontalOffset + (middle * percent))
            End If

            If Not e.ExtentHeightChange = 0 AndAlso Not e.ExtentHeightChange = e.ExtentHeight Then
                ' Keep centered verically.
                Dim percent As Double = e.ExtentHeightChange / (e.ExtentHeight - e.ExtentHeightChange)
                Dim middle As Double = e.VerticalOffset + (e.ViewportHeight / 2)
                ScrollViewer.ScrollToVerticalOffset(e.VerticalOffset + (middle * percent))
            End If

        End Sub

#End Region

        ''' <summary>
        ''' Return the offset that positions the diagram in the top-left 
        ''' corner, takes into account the zoom level.
        ''' </summary>
        Private Function GetTopLeftScrollOffset() As Point
            ' Offset that is returned.
            Dim offset As New Point()

            ' Empty offset if the diagram is empty.
            If Diagram.ActualWidth = 0 OrElse Diagram.ActualHeight = 0 Then
                Return offset
            End If

            ' Get the size of the diagram.
            Dim diagramSize As New Size( _
                Diagram.ActualWidth * Me.Zoom, _
                Diagram.ActualHeight * Me.Zoom)

            ' Calcualte the offset that positions the diagram in the top-left corner.
            offset.X = Me.ActualWidth + diagramSize.Width - (PanMargin / 2)
            offset.Y = Me.ActualHeight + diagramSize.Height - (PanMargin / 2)

            Return offset
        End Function


        ''' <summary>
        ''' Update the scroll area so the diagram can be scrolled from edge to edge.
        ''' </summary>
        Private Sub UpdateScrollSize()
            ' Nothing to do if the diagram is empty.
            If Me.ActualWidth = 0 OrElse Me.ActualHeight = 0 Then
                Return
            End If

            Dim diagramSize As New Size( _
                Diagram.ActualWidth * Me.Zoom, _
                Diagram.ActualHeight * Me.Zoom)

            ' The grid contains the diagram, set the size of the grid so it's
            ' large enough to allow the diagram to scroll from edge to edge.
            Grid.Width = Math.Max(0, (Me.ActualWidth * 2) + diagramSize.Width - PanMargin)
            Grid.Height = Math.Max(0, (Me.ActualHeight * 2) + diagramSize.Height - PanMargin)
        End Sub


        ''' <summary>
        ''' Scroll the diagram so the primary node appears at the location of the selected node.
        ''' </summary>
        Private Sub AutoScrollToSelected()
            ' Don't scroll if the diagram is empty.
            If Diagram.ActualWidth = 0 OrElse Diagram.ActualHeight = 0 Then
                Return
            End If

            ' This is the offset that will be scrolled. First get the offset 
            ' that positions the diagram in the top-left corner.
            Dim offset As Point = GetTopLeftScrollOffset()

            ' Get the location of the node that was selected.
            Dim selectedBounds As Rect = Diagram.SelectedNodeBounds

            ' See if this is the first time the diagram is being displayed.            
            If selectedBounds.IsEmpty Then
                ' First time, center the diagram in the display area.
                offset.X += ((Me.ActualWidth - (Diagram.ActualWidth * Me.Zoom)) / 2)
                offset.Y += ((Me.ActualHeight - (Diagram.ActualHeight * Me.Zoom)) / 2)
            Else
                ' Scroll the diagram so the new primary node is at the location
                ' of the previous selected node. 

                ' Offset the distance the diagram is scrolled from the 
                ' previous top-left position.
                offset.X += previousTopLeftOffset.X
                offset.Y += previousTopLeftOffset.Y

                ' Determine the distance between the two nodes.
                Dim primaryBounds As Rect = Diagram.PrimaryNodeBounds
                Dim nodeDelta As New Point()
                nodeDelta.X = (primaryBounds.Left + (primaryBounds.Width / 2)) - _
                    (selectedBounds.Left + (selectedBounds.Width / 2))
                nodeDelta.Y = (primaryBounds.Top + (primaryBounds.Height / 2)) - _
                    (selectedBounds.Top + (selectedBounds.Height / 2))

                ' Offset the distance between the two nodes.
                offset.X -= (nodeDelta.X * Me.Zoom)
                offset.Y -= (nodeDelta.Y * Me.Zoom)
            End If


            ' Scroll the diagram.
            ScrollViewer.ScrollToHorizontalOffset(Grid.Width - offset.X)
            ScrollViewer.ScrollToVerticalOffset(Grid.Height - offset.Y)

            ' Set a timer so there is a pause before centering the diagram.
            autoCenterTimer.Interval = Helper.GetAnimationDuration(AutoCenterAnimationPauseDuration)
            AddHandler autoCenterTimer.Tick, AddressOf OnAutoCenterPauseTimer
            autoCenterTimer.IsEnabled = True
        End Sub

        Private Sub OnAutoCenterPauseTimer(ByVal sender As Object, ByVal e As EventArgs)
            ' Timer only fires once.
            autoCenterTimer.IsEnabled = False

            ' Scroll the diagram so it's centered in the display area.
            AutoScrollToCenter()
        End Sub

        ''' <summary>
        ''' Center the diagram in the display area.
        ''' </summary>
        Private Sub AutoScrollToCenter()
            ' Adjust the offset so the diagram appears in the center of 
            ' the display area. First get the top-left offset.
            Dim offset As Point = GetTopLeftScrollOffset()

            ' Now adjust the offset so the diagram is centered.
            offset.X += ((Me.ActualWidth - (Diagram.ActualWidth * Me.Zoom)) / 2)
            offset.Y += ((Me.ActualHeight - (Diagram.ActualHeight * Me.Zoom)) / 2)

            ' Before auto scroll, determine the start and end 
            ' points so the scrolling can be animated.
            Dim startLocation As New Point( _
                ScrollViewer.HorizontalOffset, _
                ScrollViewer.VerticalOffset)

            Dim endLocation As New Point( _
                Grid.Width - offset.X - startLocation.X, _
                Grid.Height - offset.Y - startLocation.Y)

            ' Auto scroll the diagram.
            ScrollViewer.ScrollToHorizontalOffset(Grid.Width - offset.X)
            ScrollViewer.ScrollToVerticalOffset(Grid.Height - offset.Y)

            ' Animate the scrollings.
            AnimateDiagram(endLocation)
        End Sub

        ''' <summary>
        ''' Animate the diagram by moving from the startLocation to the endLocation.
        ''' </summary>
        Private Sub AnimateDiagram(ByVal endLocation As Point)
            ' Create the animations, nonlinear by using accelration and deceleration.
            Dim horzAnim As New DoubleAnimation(endLocation.X, 0, _
                Helper.GetAnimationDuration(AutoCenterAnimationDuration))
            horzAnim.AccelerationRatio = 0.5
            horzAnim.DecelerationRatio = 0.5

            Dim vertAnim As New DoubleAnimation(endLocation.Y, 0, _
                Helper.GetAnimationDuration(AutoCenterAnimationDuration))
            vertAnim.AccelerationRatio = 0.5
            vertAnim.DecelerationRatio = 0.5

            ' Animate the transform to make it appear like the diagram is moving.
            Dim transform As New TranslateTransform()
            transform.BeginAnimation(TranslateTransform.XProperty, horzAnim)
            transform.BeginAnimation(TranslateTransform.YProperty, vertAnim)

            ' Animate the grid (that contains the diagram) instead of the 
            ' diagram, otherwise nodes are clipped in the diagram.
            Grid.RenderTransform = transform
        End Sub


        ''' <summary>
        ''' Adjust the time slider for Shift + MouseWheel, 
        ''' adjust the zoom slider for Ctrl + MouseWheel.
        ''' </summary>
        Protected Overrides Sub OnPreviewMouseWheel(ByVal e As MouseWheelEventArgs)
            ' Zoom slider.
            If (Keyboard.Modifiers And ModifierKeys.Control) > 0 Then
                e.Handled = True
                Me.Zoom += CType(IIf((e.Delta > 0), ZoomSlider.LargeChange, -ZoomSlider.LargeChange), Double)
            End If

            MyBase.OnPreviewMouseWheel(e)
        End Sub




#Region " manual scroll "

        Protected Overrides Sub OnPreviewMouseDown(ByVal e As MouseButtonEventArgs)
            If ScrollViewer.IsMouseOver AndAlso Not Diagram.IsMouseOver Then
                ' Save starting point, used later when determining how much to scroll.
                scrollStartPoint = e.GetPosition(Me)
                scrollStartOffset.X = ScrollViewer.HorizontalOffset
                scrollStartOffset.Y = ScrollViewer.VerticalOffset

                ' Update the cursor if can scroll or not.
                Me.Cursor = CType(IIf((ScrollViewer.ExtentWidth > ScrollViewer.ViewportWidth) OrElse _
                    (ScrollViewer.ExtentHeight > ScrollViewer.ViewportHeight), _
                    Cursors.ScrollAll, Cursors.Arrow), Input.Cursor)

                Me.CaptureMouse()
            End If

            MyBase.OnPreviewMouseDown(e)
        End Sub


        Protected Overrides Sub OnPreviewMouseMove(ByVal e As MouseEventArgs)
            If Me.IsMouseCaptured Then
                ' Get the new scroll position.
                Dim point As Point = e.GetPosition(Me)

                ' Determine the new amount to scroll.
                Dim delta As New Point( _
                    CDbl(IIf((point.X > Me.scrollStartPoint.X), _
                        -(point.X - Me.scrollStartPoint.X), (Me.scrollStartPoint.X - point.X))), _
                    CDbl(IIf((point.Y > Me.scrollStartPoint.Y), _
                        -(point.Y - Me.scrollStartPoint.Y), (Me.scrollStartPoint.Y - point.Y))))

                ' Scroll to the new position.
                ScrollViewer.ScrollToHorizontalOffset(Me.scrollStartOffset.X + delta.X)
                ScrollViewer.ScrollToVerticalOffset(Me.scrollStartOffset.Y + delta.Y)
            End If

            MyBase.OnPreviewMouseMove(e)
        End Sub

        Protected Overrides Sub OnPreviewMouseUp(ByVal e As MouseButtonEventArgs)
            If Me.IsMouseCaptured Then
                Me.Cursor = Cursors.Arrow
                Me.ReleaseMouseCapture()
            End If

            MyBase.OnPreviewMouseUp(e)
        End Sub


#End Region


    End Class

End Namespace